/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat.soot.throwableset.propagation;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import soot.FastHierarchy;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.jimple.Stmt;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.jimple.toolkits.callgraph.Edge;
import br.ufpe.cin.imlm2.jbeat.soot.common.MethodTagger;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.tags.ThrowableSetTag;
import br.ufpe.cin.imlm2.jbeat.soot.traps.TrapManager;
import br.ufpe.cin.imlm2.util.Naming;

public class PropagatesCalculator extends MethodTagger {

	private boolean withClinits = false;
	private boolean withSynthetic = false;

	public PropagatesCalculator(List<String> excludeClasses,
			List<String> excludePackages, boolean rtLibraries,boolean withClinits,
			boolean withAutoGenerated) {
		super(excludeClasses, excludePackages, rtLibraries);
		this.withClinits = withClinits;
		this.withSynthetic = withAutoGenerated;
	}

	/*
	 * 
	 */
	protected void propagateToAbstractTypes(Iterable<SootClass> classes) {
		for (SootClass sootClass : classes) {
			if(sootClass.isAbstract() || sootClass.isInterface()){
				List<SootMethod> methods = sootClass.getMethods();
				for (SootMethod sootMethod : methods) {
					if(sootMethod.isAbstract()){
						FastHierarchy hie = Scene.v().getOrMakeFastHierarchy();
						Set<SootMethod> concreteMethods = hie.resolveAbstractDispatch(sootClass, sootMethod);
						for (SootMethod concreteOne : concreteMethods) {
							ThrowableSetTag conctst = (ThrowableSetTag) concreteOne.getTag(ThrowableSetTag.TAG_NAME);
							ThrowableSetTag absTst = (ThrowableSetTag) sootMethod.getTag(ThrowableSetTag.TAG_NAME);
							absTst.addAllExceptions(conctst.getThrowableSet());
						}
					}
				}				
			}

		}
	}


	private void propagateExceptionsThrough(Collection<SootClass> classes) {
		for (SootClass sootClass : classes) {
			List<SootMethod> allMethods =  sootClass.getMethods();
			for (SootMethod sootMethod : allMethods) {
				ThrowableSetTag tst = (ThrowableSetTag) sootMethod.getTag(ThrowableSetTag.TAG_NAME);
				this.propagateExceptionsFrom(tst.getThrowableSet(),sootMethod);				
			}
		}
	}

	protected void propagateExceptionsFrom(Set<SootClass> exceptions,SootMethod src){
		if(this.shouldPropagateFrom(src)) {
			CallGraph cg = Scene.v().getCallGraph();
			Iterator<Edge> edges = cg.edgesInto(src);
			Edge next;
			while(edges.hasNext()){
				next = edges.next();
				SootMethod meth = next.getSrc().method();
				Stmt stm = next.srcStmt();
				if(stm != null){
					// FIXME This check should *really* be reconsidered.
					if(meth.hasTag(ThrowableSetTag.TAG_NAME)) {
						ThrowableSetTag tst = (ThrowableSetTag) meth.getTag(ThrowableSetTag.TAG_NAME);
						Set<SootClass> throwableSet = tst.getThrowableSet();
						if(!throwableSet.containsAll(exceptions)){
							Set<SootClass> cacheSet = new HashSet<SootClass>();
							for (SootClass sootClass : exceptions) {
								if(!TrapManager.isExceptionCaughtAt(sootClass, stm, meth.retrieveActiveBody())){
									cacheSet.add(sootClass);
								}
							}
							throwableSet.addAll(cacheSet);
							this.propagateExceptionsFrom(cacheSet, meth);
						}
					}
				}
			}
		}
	}

	protected boolean shouldPropagateFrom(SootMethod m){
		if(!m.isConcrete()){
			return false;
		} else if(!this.withClinits && m.getName().equals("<clinit>")){
			return false;
		} else if(!this.withSynthetic && Naming.isMethodSynthetic(m.getName())){
			return false;
		} else if(!this.targetSet.contains(m.getDeclaringClass())){
			return false;
		}
		return true;
	}

	protected void checkTags(SootMethod meth){
		if(!meth.hasTag(ThrowableSetTag.TAG_NAME)){
			meth.addTag(new ThrowableSetTag());
		}
	}


	protected void tag(Collection<SootClass> classes) {
		System.err.println("Exception propagation beginning");
		this.propagateExceptionsThrough(classes);
		this.propagateToAbstractTypes(classes);
		System.err.println("Exception propagation finished");
	}

}
